// RedJey Graphics User Interface Library for J2ME Platform.
// Copyright (C) 2008 Yevgen Krapiva
//
// This library is free software; you can redistribute
// it and/or modify it under the terms of the
// GNU Lesser General Public License as published
// by the Free Software Foundation; either version 3.0
// of the License, or any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package com.incrediblesoft.redjey;

/**
 * @author Yevgen Krapiva<br>
 *         Homepage: http://code.google.com/p/redjey/<br>
 *         Feedback: ykrapiva@gmail.com<br>
 *         Copyright (C) 2008 Yevgen Krapiva<br>
 */

import com.incrediblesoft.redjey.borders.SimpleBorder;
import com.incrediblesoft.redjey.backgrounds.SimpleBackground;
import com.incrediblesoft.redjey.event.*;

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
import java.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;


public class UIManager
{
    /*
     * Key code for left soft key
     */
    public static int KEY_SOFT_LEFT = -6;
    /*
     * Key code for right soft key
     */
    public static int KEY_SOFT_RIGHT = -7;
    /**
     * Key code for selection key
     */
    public static int KEY_SELECT = -5;
    /**
     * Key code for the LEFT game action.
     */
    public static int KEY_LEFT = -3;
    /**
     * Key code for the RIGHT game action.
     */
    public static int KEY_RIGHT = -4;
    /**
     * Key code for the UP game action.
     */
    public static int KEY_UP = -1;
    /**
     * Key code for the DOWN game action.
     */
    public static int KEY_DOWN = -2;
    /**
     * Key code for CLEAR key
     */
    public static int KEY_CLEAR = -8;

    /**
     * Localized strings.
     * Used, for example, in text field edit box.
     */
    public static String APPLY_CMD = "Apply";
    public static String CANCEL_CMD = "Cancel";

    //Internal stuff
    private static Vector windows = new Vector();
    private static Display display;
    private static Canvas canvas;
    private static MIDlet midlet;
    private static Hashtable resHash = new Hashtable();
    protected static TextBox box;
    protected static Command applyCmd, cancelCmd;
    private static Vector paintEventQueue = new Vector();
    private static Vector eventQueue = new Vector();
    private static Component prevPressedComponent;
    private static Hashtable mostRecentFocusOwners = new Hashtable();
    private static final Vector keyListeners = new Vector();

    //Cursor stuff
    private static int prevCurX, prevCurY;

    /**
     * Styles cache
     */
    private static Hashtable styles_hash = new Hashtable();

    /**
     * Default style class name
     */
    public static final String DEFAULT_SET = "default";

    /**
     * String representation of component state
     */
    private static final String NORMAL_STR = "normal";
    private static final String FOCUSED_STR = "focused";
    private static final String PRESSED_STR = "pressed";
    private static final String DISABLED_STR = "disabled";

    private UIManager()
    {
    }

    /**
     * Assignes MIDlet class. Neccessary for operating.
     * @param midlet_
     */
    public static void setMIDlet(MIDlet midlet_)
    {
        midlet = midlet_;
        display = Display.getDisplay(midlet);
        box = new TextBox(null, null, 255, javax.microedition.lcdui.TextField.ANY);
        applyCmd = new Command(APPLY_CMD, Command.OK, 1);
        cancelCmd = new Command(CANCEL_CMD, Command.CANCEL, 1);
        box.addCommand(applyCmd);
        box.addCommand(cancelCmd);
    }

    public static MIDlet getMIDlet()
    {
        return midlet;
    }

    public static Display getDisplay()
    {
        return display;
    }

    public static Canvas getCanvas()
    {
        return canvas;
    }

    public static void setCanvas(Canvas canvas_)
    {
        canvas = canvas_;
    }

    /**
     * Focuses the Component after aComponent.
     *
     * @param aComponent the Component that is the basis for the focus
     *        traversal operation
     */
    public static void focusNextComponent(Component aComponent)
    {
        if (aComponent != null)
            aComponent.transferFocus();
    }

    /**
     * Focuses the Component before aComponent.
     *
     * @param aComponent the Component that is the basis for the focus
     *        traversal operation
     */
    public static void focusPreviousComponent(Component aComponent)
    {
        if (aComponent != null)
            aComponent.transferFocusBackward();
    }

    /**
     * Focuses the Component after the current focus owner.
     */
    public static void focusNextComponent() {
        Component focusOwner = getFocusOwner();
        if (focusOwner != null) {
            focusNextComponent(focusOwner);
        }
    }

    /**
     * Focuses the Component before the current focus owner.
     */
    public static void focusPreviousComponent() {
        Component focusOwner = getFocusOwner();
        if (focusOwner != null) {
            focusPreviousComponent(focusOwner);
        }
    }

    /**
     * Moves the focus up one focus traversal cycle.
     *
     * @param aComponent the Component that is the basis for the focus
     *        traversal operation
     */
    public static void focusUpCycle(Component aComponent)
    {
        if (aComponent != null)
            aComponent.transferFocusUpCycle();
    }

    /**
     * Moves the focus down one focus traversal cycle.
     *
     * @param aContainer the Container that is the basis for the focus
     *        traversal operation
     */
    public static void focusDownCycle(Container aContainer)
    {
        if (aContainer != null && aContainer.isFocusCycleRoot())
            aContainer.transferFocusDownCycle();
    }

    /**
     * Moves the focus up one focus traversal cycle from the current focus
     * owner.
     */
    public static void focusUpCycle() {
        Component focusOwner = getFocusOwner();
        if (focusOwner != null) {
            focusUpCycle(focusOwner);
        }
    }


    /**
     * Moves the focus down one focus traversal cycle from the current focus
     * owner, if and only if the current focus owner is a Container that is a
     * focus cycle root.
     */
    public static void focusDownCycle() {
        Component focusOwner = getFocusOwner();
        if (focusOwner != null)
        {
            Container cycleRoot = focusOwner.getFocusCycleRootAncestor();
            if (cycleRoot != null)
                focusDownCycle(cycleRoot);
        }
    }

    /**
     * Returns the current focus owner component
     * @return
     */
    public static Component getFocusOwner()
    {
        Window activeWindow = getActiveWindow();
        if (activeWindow == null) return null;

        Component focusOwner = (Component) mostRecentFocusOwners.get(activeWindow);
        return focusOwner;
    }

    /**
     * Sets the current focus owwner.
     * @param component Component that will receive focus
     * @return
     */
    public static boolean setMostRecentFocusOwner(Component component)
    {
        if (component.isVisible() && component.isEnabled() && component.isFocusable())
        {
            Component window = component;
            while (window != null && !(window instanceof Window))
                window = window.parent;

            if (window != null)
            {
                Component oldFocusOwner = (Component) mostRecentFocusOwners.get(window);
                if (oldFocusOwner != null && oldFocusOwner != component)
                {
                    oldFocusOwner.setState(Component.NORMAL);
                    oldFocusOwner.repaint();
                    oldFocusOwner.dispatchEvent(new FocusEvent(oldFocusOwner, FocusEvent.FOCUS_LOST));
                }

                mostRecentFocusOwners.put((Window) window, component);
                component.setState(Component.FOCUSED);
                component.repaint();
                component.dispatchEvent(new FocusEvent(component, FocusEvent.FOCUS_GAINED));
                return true;
            }
        }
        return false;
    }

    private static void processKeyEvent(int eventType, int keycode)
    {
        KeyEvent keyEvent = new KeyEvent(null, eventType, keycode);

        for (int i=0; i<keyListeners.size(); i++)
        {
            Component keyListener = (Component) keyListeners.elementAt(i);
            keyEvent.setSource(keyListener);
            keyListener.dispatchEvent(keyEvent);
            if (keyEvent.isConsumed()) return;
        }

        Component focusOwner = getFocusOwner();

        switch(eventType)
        {
            case KeyEvent.KEY_PRESSED:
                if (keycode == KEY_DOWN)
                    focusNextComponent();
                else if (keycode == KEY_UP)
                    focusPreviousComponent();
                else if (keycode == KEY_LEFT)
                    focusUpCycle();
                else if (keycode == KEY_RIGHT)
                    focusDownCycle();
                else if (focusOwner != null && keycode == KEY_SELECT)
                    focusOwner.doClick();
                break;
        }

        if (focusOwner != null)
        {
            keyEvent.setSource(focusOwner);
            focusOwner.dispatchEvent(keyEvent);
        }
    }

    public static void addKeyListener(Component keyListener)
    {
        if (!keyListeners.contains(keyListener))
            keyListeners.addElement(keyListener);
    }

    public static void removeKeyListener(Component keyListener)
    {
        keyListeners.removeElement(keyListener);
    }

    /**
     * Places given even in event queue.
     * Event queue is processed every tick() method is called.
     * @param event Event to queue
     */
    public static void postEvent(AbstractEvent event)
    {
        eventQueue.addElement(event);
    }

    /**
     * Places a new window on top of others
     * @param window Window
     */
    public static void addWindow(Window window)
    {
        insertWindowAt(window, windows.size());
    }

    /**
     * Inserts window between some others windows.
     * @param window Window
     * @param index Index to place in. Think about it as Z-order index.
     */
    public static void insertWindowAt(Window window, int index)
    {
        Window prevFocusedWindow = getActiveWindow();

        windows.removeElement(window);

        //Insert new window
        windows.insertElementAt(window, index);

        if (index == 0)
        {
            //First, old focused window will receive FOCUS_LOST event
            if (prevFocusedWindow != null && prevFocusedWindow != window)
            {
                postEvent(new FocusEvent(prevFocusedWindow, FocusEvent.FOCUS_LOST));
            }

            //Second, new window on top will receive FOCUS_GAINED event
            postEvent(new FocusEvent(window, FocusEvent.FOCUS_GAINED));

            Component focusOwner = (Component) mostRecentFocusOwners.get(window);
            if (focusOwner == null)
            {
                focusOwner = window.getComponentAfter(null);
                if (focusOwner != null) focusOwner.requestFocus();
            }
        }

        window.repaint();
    }

    /**
     * Returns the Z-order index of window.
     * @param window Window
     * @return Z-order index.
     */
    public static int indexOf(Window window)
    {
        return windows.indexOf(window);
    }

    /**
     * Removes window from view.
     * @param window Window
     */
    public static void removeWindow(Window window)
    {
        int index = windows.indexOf(window);
        if (index == -1) return;

        windows.removeElementAt(index);

        Component focusOwner = (Component) mostRecentFocusOwners.get(window);
        if (focusOwner != null) focusOwner.setState(Component.NORMAL);
        mostRecentFocusOwners.remove(window);

        if (index == 0)
        {
            postEvent(new FocusEvent(window, FocusEvent.FOCUS_LOST));
            Window activeWindow = getActiveWindow();
            if (activeWindow != null)
                activeWindow.dispatchEvent(new FocusEvent(activeWindow, FocusEvent.FOCUS_GAINED));
            focusOwner = getFocusOwner();
            if (focusOwner != null)
                setMostRecentFocusOwner(focusOwner);
        }

        repaint();
    }

    /**
     * Returns the total number of windows in view.
     * @return Number of windows
     */
    public static int getNumWindows()
    {
        return windows.size();
    }

    /**
     * Removes all windows from view.
     */
    public static void removeAllWindows()
    {
        windows.removeAllElements();
    }

    /**
     * Returns current active window
     */
    public static Window getActiveWindow()
    {
        return  windows.size() > 0 ? (Window) windows.firstElement() : null;
    }

    /**
     * Sets current active window
     */
    public static void setActiveWindow(Window window)
    {
        insertWindowAt(window, 0);
    }

    /**
     * Need help here !
     * @param event
     */
    public static void postPaintEvent(PaintEvent event)
    {
        //System.out.println("postPaintEvent source "+event.getSource()+"  rect: "+event.getUpdateRect());

        /*int index = 0;
        while ((index = queueContainsSource(index, event.getSource())) != -1)
        {
            Rectangle newRect = mergePaintUpdateEvents(event, (PaintEvent) paintEventQueue.elementAt(index));
            if (newRect != null)
            {
                event.setUpdateRect(newRect);
                paintEventQueue.removeElementAt(index);
            }
            else
                index++;
        }*/
       // int index = windows.indexOf(event.getSource());
       // if (index != -1)
        //{
        if (!event.getUpdateRect().isEmpty())
            paintEventQueue.addElement(event);
        /*    for ( --index; index >= 0; index--)
            {
                Window windowOnTop = (Window) windows.elementAt(index);
                Rectangle intersectedRect = event.getUpdateRect().intersection(windowOnTop.getBounds());
                if (intersectedRect )
            }
        } */
    }

    /*private static Rectangle mergePaintUpdateEvents(PaintEvent a, PaintEvent b)
    {
        Rectangle aRect = a.getUpdateRect();
        Rectangle bRect = b.getUpdateRect();
        if (bRect.contains(aRect))
        {
            return bRect;
        }
        if (aRect.contains(bRect))
        {
            return aRect;
        }
        return null;
    }

    private static int queueContainsSource(int fromIndex, Component source)
    {
        for (int i=fromIndex; i< paintEventQueue.size(); i++)
            if (((PaintEvent) paintEventQueue.elementAt(i)).getSource() == source)
                return i;
        return -1;
    } */

    /**
     * Must be called every frame.
     * @param dt milliseconds passed from previous call
     */
    public static void tick(int dt)
    {
        if (eventQueue.size() > 0)
            processEventQueue();

        for (int i=0; i<windows.size(); i++)
        {
            ((Window) windows.elementAt(i)).tick(dt);
        }
    }

    /**
     * Processes event queue
     * NEED A GENERAL HELP HERE !
     * Some of its functionality is duplicated in Container class.
     * I still can't get how to realize all this stuff properly.
     */
    private static void processEventQueue()
    {
        Window currentFocusedWindow = windows.size() > 0 ? (Window) windows.firstElement() : null;

        //Dispatch all not consumed events to destintations
        for (int i=0; i< eventQueue.size(); i++)
        {
            AbstractEvent event = (AbstractEvent) eventQueue.elementAt(i);
            if (event.isConsumed()) continue;

            //Get source
            Component source = event.getSource();
            //If source object is defined
            if (source != null)
            {
                //Dispatch event directly to source
                source.dispatchEvent(event);
            }
            //Else if source not defined and this is a MouseEvent
            else if (event.getEventType() == AbstractEvent.MOUSE_EVENT)
            {
                MouseEvent mouseEvent = (MouseEvent) event;

                //Look through each window
                for (int n=0; n<windows.size(); n++)
                {
                    Window window = (Window) windows.elementAt(n);

                    //Find window under the mouse cursor position
                    if (window.getBounds().inside(mouseEvent.getX(), mouseEvent.getY()))
                    {
                        if (currentFocusedWindow != null && currentFocusedWindow != window && currentFocusedWindow.isModal)
                        {
                            //do nothing
                            continue;
                        }
                        else if (currentFocusedWindow != window)
                        {
                            //Bring the new window on top
                            //System.out.println("Bringing window "+window+" on top");
                            insertWindowAt(window, 0);
                        }

                        //Translate mouse coords into window coordinate system
                        mouseEvent = retargetMouseEvent(mouseEvent, window);

                        //Check different event types
                        switch(event.getID())
                        {
                            case MouseEvent.MOUSE_PRESSED:
                                //Remember the object over which mouse was pressed, to generate later MOUSE_CLICKED event
                                prevPressedComponent = window.getComponentAt(mouseEvent.getX(), mouseEvent.getY(), true);
                                //System.out.println("prevPressedComponent is "+prevPressedComponent+"  with mouse coords ("+mouseEvent.getX()+","+mouseEvent.getY()+")");

                                //If the previuos window was a PopupMenu and the new window doesn't belong
                                //to menues in any way, we need to close opened menues.
                                if (currentFocusedWindow != null && currentFocusedWindow instanceof PopupMenu && !(window instanceof PopupMenu))
                                {
                                    //System.out.println("AAAAAAA");
                                    //Component targetComponent = window.getComponentAt(mouseEvent.getX(), mouseEvent.getY(), true);
                                    //System.out.println("targetComponent = "+targetComponent);
                                    PopupMenu popup = (PopupMenu) currentFocusedWindow;
                                    Component root = popup.getRoot();
                                    if (root != prevPressedComponent)
                                    {
                                        if (root instanceof PopupMenu)
                                            ((PopupMenu) root).setVisible(false);
                                        else if (root instanceof Menu)
                                            ((Menu) root).close();
                                    }

                                }

                                if (prevPressedComponent != null)
                                {
                                    mouseEvent = retargetMouseEvent(mouseEvent, prevPressedComponent);
                                    prevPressedComponent.dispatchEvent(mouseEvent);
                                }
                                break;

                            case MouseEvent.MOUSE_RELEASED:
                                //First, send the original event
                                //System.out.println("AAAAA ("+mouseEvent.getX()+","+mouseEvent.getY()+")");
                                //window.dispatchEvent(mouseEvent);

                                //Find targeted component looking to the deep of tree
                                Component targetComponent = window.getComponentAt(mouseEvent.getX(), mouseEvent.getY(), true);
                                if (targetComponent !=  null)
                                    targetComponent.dispatchEvent(new MouseEvent(targetComponent, MouseEvent.MOUSE_RELEASED));
                                //System.out.println("targetComponent is "+targetComponent+"  with mouse coords ("+mouseEvent.getX()+","+mouseEvent.getY()+")");
                                //if targeted component is the same it was in MOUSE_PRESSED event, send MOUSE_CLICKED event to it.
                                if (targetComponent == prevPressedComponent && targetComponent != null)
                                    targetComponent.dispatchEvent(new MouseEvent(targetComponent, MouseEvent.MOUSE_CLICKED));
                                else if (prevPressedComponent != null && prevPressedComponent.isVisible() && prevPressedComponent.isEnabled())
                                {
                                    if (prevPressedComponent.isFocused() && prevPressedComponent.getState() != Component.FOCUSED)
                                    {
                                        prevPressedComponent.setState(Component.FOCUSED);
                                        prevPressedComponent.repaint();
                                    }
                                    else if (prevPressedComponent.getState() != Component.NORMAL)
                                    {
                                        prevPressedComponent.setState(Component.NORMAL);
                                        prevPressedComponent.repaint();
                                    }

                                }
                                //prevPressedComponent.dispatchEvent(new MouseEvent(prevPressedComponent, MouseEvent.MOUSE_RELEASED));
                                //prevPressedComponent.dispatchEvent(new FocusEvent(prevPressedComponent, FocusEvent.FOCUS_LOST));

                                prevPressedComponent = null;
                                break;

                            default:
                                //At last send the original event
                                window.dispatchEvent(mouseEvent);
                        }
                        break;
                    }
                }
            }
        }

        //clear event queue
        eventQueue.removeAllElements();
    }

    private synchronized static void processPaintEventQueue(Graphics g)
    {
        int oldTransX;
        int oldTransY;
        int oldClipX;
        int oldClipY;
        int oldClipWidth;
        int oldClipHeight;

        for (int i=0; i< paintEventQueue.size(); i++)
        {
            PaintEvent event = (PaintEvent) paintEventQueue.elementAt(i);

            Window source = (Window) event.getSource();

            int index = windows.indexOf(source);
            if (index == -1) index = windows.size()-1;

            Rectangle absoluteUpdateRect = event.getUpdateRect();
            if (source != null)
                absoluteUpdateRect.setLocation(source.x + absoluteUpdateRect.x, source.y + absoluteUpdateRect.y);

            for ( ; index >= 0; index--)
            {
                Window windowOnTop = (Window) windows.elementAt(index);
                Rectangle rect = windowOnTop.getBounds().intersection(absoluteUpdateRect);

                if (!rect.isEmpty())
                {
                    //System.out.println("Painting "+windowOnTop.toString()+"  at "+rect+" -> "+System.currentTimeMillis());

                    oldTransX = g.getTranslateX();
                    oldTransY = g.getTranslateY();
                    oldClipX = g.getClipX();
                    oldClipY = g.getClipY();
                    oldClipWidth = g.getClipWidth();
                    oldClipHeight = g.getClipHeight();

                    rect.x -= windowOnTop.x;
                    rect.y -= windowOnTop.y;

                    g.translate(windowOnTop.getX(), windowOnTop.getY());
                    g.setClip(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
                    //dbg.setClip(0, 0, rect.getWidth(), rect.getHeight());

                    //System.out.println("Painting "+source+"  at "+new Date());
                    windowOnTop.paint(g);

                    g.translate(oldTransX - g.getTranslateX(), oldTransY - g.getTranslateY());
                    g.setClip(oldClipX, oldClipY, oldClipWidth, oldClipHeight);
                }
            }
        }

        paintEventQueue.removeAllElements();
    }

    public static MouseEvent retargetMouseEvent(MouseEvent origEvent, Component newSource)
    {
        int new_x = origEvent.getX() - newSource.getX();// - (newSource.parent != null ? newSource.parent.getCurrentStyle().getClientOffsetX() : 0);
        int new_y = origEvent.getY() - newSource.getY();// - (newSource.parent != null ? newSource.parent.getCurrentStyle().getClientOffsetY() : 0);

        MouseEvent newEvent = new MouseEvent(newSource, origEvent.getID(), new_x, new_y, origEvent.getValue());
        return newEvent;
    }

    public static void keyPressed(int keycode)
    {
        processKeyEvent(KeyEvent.KEY_PRESSED, keycode);
    }

    public static void keyRepeated(int keycode)
    {
        processKeyEvent(KeyEvent.KEY_REPEATED, keycode);
    }

    public static void keyReleased(int keycode)
    {
        processKeyEvent(KeyEvent.KEY_RELEASED, keycode);
    }

    public static void pointerPressed(int x, int y)
    {
        //System.out.println("pointerPressed("+x+","+y+")");
        prevCurX = x;
        prevCurY = y;

        postEvent(new MouseEvent(null, MouseEvent.MOUSE_PRESSED, x, y, 0));
    }

    public static void pointerReleased(int x, int y)
    {
        //System.out.println("pointerReleased("+x+","+y+")");
        prevCurX = x;
        prevCurY = y;

        postEvent(new MouseEvent(null, MouseEvent.MOUSE_RELEASED, x, y, 0));
        //postEvent(new MouseEvent(null, MouseEvent.MOUSE_MOVED, curX , curY, 0));
    }

    public static void pointerDragged(int x, int y)
    {
        int dx = prevCurX - x;
        int dy = prevCurY - y;

        prevCurX = x;
        prevCurY = y;

        if (dx != 0)
            postEvent(new MouseEvent(null, MouseEvent.MOUSE_SCROLL_HORIZONTAL, x, y, dx));
        if (dy != 0)
            postEvent(new MouseEvent(null, MouseEvent.MOUSE_SCROLL_VERTICAL, x, y, dy));
    }

    /**
     * Causes screen refresh
     */
    public static void repaint()
    {
        postPaintEvent(new PaintEvent(null, new Rectangle(0, 0, canvas.getWidth(), canvas.getHeight())));
    }

    public static void showNotify()
    {
        //leftKeyPressed = rightKeyPressed = upKeyPressed = downKeyPressed = selectKeyPressed = false;
    }

    public static void hideNotify()
    {
        //leftKeyPressed = rightKeyPressed = upKeyPressed = downKeyPressed = selectKeyPressed = false;
    }

    public synchronized static void paint(Graphics g)
    {
        if (paintEventQueue.size() > 0)
        {
            processPaintEventQueue(g);
        }
    }

    public static void cacheImage(String imagePath) throws IOException
    {
        if (!resHash.containsKey(imagePath))
        {
            resHash.put(imagePath, Image.createImage(imagePath));
        }
    }

    public static Image getImage(String imagePath)
    {
        return (Image) resHash.get(imagePath);
    }

    public static CustomImage getCustomImage(String imagePath)
    {
        return new CustomImage((Image) resHash.get(imagePath));
    }

    public static void removeAllRegisteredStyles()
    {
        styles_hash.clear();
    }

    public static Hashtable getStylesScheme()
    {
        return styles_hash;
    }

    public static void setStylesScheme(Hashtable scheme)
    {
        styles_hash = scheme;
    }

    /**
     * Registers style class
     * @param componentName component name. See Component.getName()
     * @param className class name. The default is UIManager.DEFAULT_SET
     * @param styles Styles array. Each element in array corresponds to the specific state of component. See Component.getState()
     */
    public static void registerStyleClass(String componentName, String className, Style[] styles)
    {
        if (!styles_hash.containsKey(componentName))
        {
            styles_hash.put(componentName, new Hashtable());
        }

        Hashtable styleClassHash = (Hashtable) styles_hash.get(componentName);
        if (styleClassHash != null)
        {
            styleClassHash.put(className, styles);
        }
    }

    /**
     * Registers single style
     * @param componentName Component name. see Component.getName()
     * @param className Class name to register the style in.
     * @param componentState Component state.
     * @param style Style itself.
     */
    public static void registerStyle(String componentName, String className, int componentState, Style style)
    {
        boolean reput = false;

        if (!styles_hash.containsKey(componentName))
        {
            styles_hash.put(componentName, new Hashtable());
        }

        Hashtable styleSetHash = (Hashtable) styles_hash.get(componentName);
        if (!styleSetHash.containsKey(className))
        {
            styleSetHash.put(className, new Style[1]);
        }

        Style[] styles = (Style[]) styleSetHash.get(className);
        if (styles.length <= componentState)
        {
            Style[] oldStyles = styles;
            styles = new Style[componentState+1];
            for (int i=0; i<oldStyles.length; i++)
                styles[i] = oldStyles[i];
            reput = true;
        }
        styles[componentState] = style;
        if (reput)
            styleSetHash.put(className, styles);
    }

    public static Style[] getStyleClass(String componentName)
    {
        return getStyleClass(componentName, DEFAULT_SET);
    }


    public static Style[] getStyleClass(String componentName, String className)
    {
        Hashtable styleClassHash = (Hashtable) styles_hash.get(componentName);
        Style[] styleSet = null;

        if (styleClassHash != null)
        {
            styleSet = (Style[]) styleClassHash.get(className);
        }

        return styleSet;
    }

    protected static Style[] getStyleClass4Sure(String componentName, String className)
    {
        Style[] styleSet = getStyleClass(componentName, className);

        if (styleSet == null && !className.equals(DEFAULT_SET))
        {
            className = DEFAULT_SET;
            styleSet = getStyleClass(componentName, className);
        }

        if (styleSet == null && !componentName.equals("Component"))
            styleSet = getStyleClass("Component", className);

        if (styleSet == null)
            System.out.println("Cannot find style class "+componentName+"."+className);

        return styleSet;
    }

    public static Style getStyle(String componentName, String className, int componentState)
    {
        Style[] styleSet = getStyleClass(componentName, className);
        if (styleSet != null && styleSet.length > componentState)
            return styleSet[componentState];
        else
            return null;
    }

    /**
     * Parses styles description file and registers all styles listed in that
     * @param path File path
     * @param printDebugInfo  Prints debug info to System.out
     * @throws IOException As usual
     */
    public static void loadStylesFromResource(String path, boolean printDebugInfo) throws IOException
    {
        InputStream is = null;
        ByteArrayOutputStream baos = null;

        try
        {
            is = UIManager.getMIDlet().getClass().getResourceAsStream(path);
            baos = new ByteArrayOutputStream();

            int n;
            byte[] buf = new byte[1024];
            while ((n = is.read(buf)) != -1)
                baos.write(buf, 0, n);

            baos.close();
            is.close();
            buf = null;

            String text = new String(baos.toByteArray());
            loadStyles(text, printDebugInfo);
        }
        finally
        {
            if (is != null) try {is.close();} catch (IOException ex) { /* ignore */ }
            if (baos != null) try {baos.close();} catch (IOException ex) { /* ignore */ }
        }

        baos = null;
        is = null;
    }

    public static void loadStyles(String temptext, boolean printDebugInfo)
    {
        StringBuffer buf = new StringBuffer();
        String line;
        int index, fromIndex = 0;

        //First, filter all lines that starts with '#' character
        while ((index = temptext.indexOf('\n', fromIndex)) != -1)
        {
            line = temptext.substring(fromIndex, index).trim();
            if (!line.startsWith("#")) buf.append(line).append("\n");
            fromIndex = index+1;
        }

        line = temptext.substring(fromIndex).trim();
        if (!line.startsWith("#")) buf.append(line);

        String text = buf.toString();
        buf = null;
        line = null;

        //Second, read all entries
        fromIndex = 0;

        while ((index = text.indexOf('{', fromIndex)) != -1)
        {
            //Style name
            String componentName;
            //Style class name
            String className;
            //Component state
            int componentState;

            //Read definition. Definition format is 'ComponentName.StyleClassName:ComponentState'
            String definition = text.substring(fromIndex, index).trim();

            //Check if style description wants to copy the other style description
            Object[] extendedClassDesc = null;
            int extendsIndex = definition.indexOf("extends");
            if (extendsIndex != -1)
            {
                //Get style description that we want to copy
                extendedClassDesc = parseStyleDescription(definition.substring(extendsIndex+7).trim());
                definition = definition.substring(0, extendsIndex).trim();
            }

            //Get this (current) style description
            Object[] thisClassDesc = parseStyleDescription(definition);
            componentName = (String) thisClassDesc[0];
            className = (String) thisClassDesc[1];
            componentState = ((Integer) thisClassDesc[2]).intValue();

            Style[] styleSet = null;

            //If we need to copy another style
            if (extendedClassDesc != null)
            {
                //This is component state of copied style
                int extendedStyleState = ((Integer) extendedClassDesc[2]).intValue();

                //If both current and copied component states are not defined,
                //this means that we need to copy all component states.
                if (extendedStyleState == -1 && componentState == -1)
                {
                    //Get entire style class
                    Style[] extendedStyleClass = getStyleClass((String) extendedClassDesc[0], (String) extendedClassDesc[1]);
                    //Copy style class
                    styleSet = new Style[extendedStyleClass.length];
                    for (int i=0; i<extendedStyleClass.length; i++)
                    {
                        if (extendedStyleClass[i] != null)
                            styleSet[i] = extendedStyleClass[i].clone();
                    }
                }
                else
                {
                    if (extendedStyleState == -1) extendedStyleState = Component.NORMAL;
                    if (componentState == -1) componentState = Component.NORMAL;

                    Style extendedStyle = getStyle((String) extendedClassDesc[0], (String) extendedClassDesc[1], extendedStyleState);
                    styleSet = new Style[componentState+1];
                    styleSet[componentState] = extendedStyle.clone();
                }
            }
            else
            {
                if  (componentState == -1)
                    componentState = Component.NORMAL;
            }

            if (styleSet == null)
            {
                Style style = getStyle(componentName, className, componentState);
                if (style == null) style = new Style();

                styleSet = new Style[componentState+1];
                styleSet[componentState] = style; 
            }

            if (printDebugInfo)
                System.out.println(componentName+"."+className+":"+(componentState == -1 ? "all" : ""+componentState));

            int index2 = indexOfIgnoringBrackets(text, '}', index+1);

            if (!componentName.startsWith("#"))
            {
                Hashtable desc = (Hashtable) parseValue(text, index, index2);
                Enumeration en = desc.keys();

                while (en.hasMoreElements())
                {
                    String name = (String) en.nextElement();
                    Object value = desc.get(name);

                    if (printDebugInfo)
                        System.out.println("   "+name+":"+value);

                    for (int i=0; i<styleSet.length; i++)
                    {
                        Style style = styleSet[i];
                        if (style == null) continue;

                        if (name.equals("padding-left"))
                            style.paddingLeft = Integer.parseInt((String) value);
                        else if (name.equals("padding-right"))
                            style.paddingRight = Integer.parseInt((String) value);
                        else if (name.equals("padding-top"))
                            style.paddingTop = Integer.parseInt((String) value);
                        else if (name.equals("padding-bottom"))
                            style.paddingBottom = Integer.parseInt((String) value);
                        else if (name.equals("padding-vertical"))
                            style.paddingVertical = Integer.parseInt((String) value);
                        else if (name.equals("padding-horizontal"))
                            style.paddingHorizontal = Integer.parseInt((String) value);
                        else if (name.equals("margin-left"))
                            style.marginLeft = Integer.parseInt((String) value);
                        else if (name.equals("margin-right"))
                            style.marginRight = Integer.parseInt((String) value);
                        else if (name.equals("margin-top"))
                            style.marginTop = Integer.parseInt((String) value);
                        else if (name.equals("margin-bottom"))
                            style.marginBottom = Integer.parseInt((String) value);
                        else if (name.equals("margin"))
                            style.marginTop = style.marginBottom = style.marginLeft = style.marginRight = Integer.parseInt((String) value);
                        else if (name.equals("padding"))
                            style.paddingTop = style.paddingBottom = style.paddingLeft = style.paddingRight = Integer.parseInt((String) value); 
                        else
                        {
                            if (name.equals("background-simple"))
                            {
                                style.background = new SimpleBackground((Hashtable)value);
                            }
                            else if (name.equals("border-simple"))
                            {
                                style.border = new SimpleBorder((Hashtable)value);
                            }
                            else if (name.equals("font"))
                            {
                                style.font = CustomFont.getFont(value);
                            }
                            else
                            {
                                style.attributes.put(name, value);
                            }
                        }
                    }
                }

                for (int i=0; i<styleSet.length; i++)
                {
                    if (styleSet[i] != null)
                        registerStyle(componentName, className, i, styleSet[i]);
                }
            }
            fromIndex = index2+1;
        }
    }

    private static Object[] parseStyleDescription(String s)
    {
        String componentName;
        String className = DEFAULT_SET;
        String componentStateStr = null;
        String definition = s;

        int stateIndex = definition.indexOf(':');
        if (stateIndex != -1)
        {
            componentStateStr = definition.substring(stateIndex+1).trim();
            definition = definition.substring(0, stateIndex).trim();
        }

        int classIndex = definition.indexOf('.');
        if (classIndex != -1)
        {
            className = definition.substring(classIndex+1).trim();
            definition = definition.substring(0, classIndex).trim();
        }

        componentName = definition;


        int styleState = -1;

        if (componentStateStr != null)
        {
            if (componentStateStr.equals(NORMAL_STR))
                styleState = Component.NORMAL;
            else if (componentStateStr.equals(FOCUSED_STR))
                styleState = Component.FOCUSED;
            else if (componentStateStr.equals(PRESSED_STR))
                styleState = Component.PRESSED;
            else if (componentStateStr.equals(DISABLED_STR))
                styleState = Component.DISABLED;
            else
                styleState = Integer.parseInt(componentStateStr);
        }

        return new Object[] {componentName, className, new Integer(styleState)};
    }

    private static Object parseValue(String s)
    {
        return parseValue(s, 0, s.length());
    }

    /**
     * Parses some value
     * @param s Text that represents value
     * @param fromIndex Index to start with
     * @param toIndex Index to end with
     * @return Hashtable if value has nested attributes, else returns the same 's' variable that was mentioned above
     */
    private static Object parseValue(String s, int fromIndex, int toIndex)
    {
        Hashtable table;

        //if value starts with bracket
        if (s.charAt(fromIndex) == '{')
        {
            table = new Hashtable();
            fromIndex++;

            int indexAttr = 0;
            //Move along all attributes
            while ((indexAttr = indexOfIgnoringBrackets(s, ';', fromIndex)) != -1 && indexAttr < toIndex)
            {
                String attribute = s.substring(fromIndex, indexAttr).trim();
                int indexDelimeter = attribute.indexOf(':');

                //Getting name and value
                String name = attribute.substring(0, indexDelimeter).trim();
                String value = attribute.substring(indexDelimeter+1).trim();
                if (!name.startsWith("#"))
                {
                    //Recursively parse value and put this attribute to hashtable
                    Object parsedValue = parseValue(value);

                    //Recognize common objects
                    if (name.equals("image"))
                    {
                        parsedValue = new CustomImage((Hashtable) parsedValue);
                    }

                    table.put(name, parsedValue);
                }
                //else
                //System.out.println("skipped "+name+" : "+value);

                fromIndex = indexAttr+1;
            }

            return table;
        }
        else
        {
            String attribute = s.substring(fromIndex);
            int indexDelimeter = attribute.indexOf(':');
            Object parsedValue = null;

            if (indexDelimeter != -1)
            {
                String name = attribute.substring(0, indexDelimeter).trim();
                String value = attribute.substring(indexDelimeter+1).trim();
                if (name.equals("image")) parsedValue = new CustomImage((Hashtable) parseValue(value));
            }
            else parsedValue = s;

            return parsedValue;
        }
    }

    /**
     * Searches for character in the { .. } scope
     * @param s Text to search in
     * @param ch Character to find
     * @param fromIndex Index to begin with
     * @return Index of character, or -1 if it was not found in the current scope.
     */
    private static int indexOfIgnoringBrackets(String s, char ch, int fromIndex)
    {
        int bracketsCount = 0;
        char c;

        for (int i=fromIndex; i<s.length(); i++)
        {
            c = s.charAt(i);
            if (c == ch && bracketsCount == 0)
                return i;
            else if (c == '{')
                bracketsCount++;
            else if (c == '}')
                bracketsCount--;
        }

        return -1;
    }

    public static int floor(int a, int b)
    {
        int c = a / b;
        if (a % b > 0) c++;
        return c;
    }



}
